package com.yiba.vpn.protocal.tcpip;


import com.yiba.vpn.utils.CommonMethods;

/**
 * 封装了TCPHeader
 */
public class TCPHeader {
    /**
     * ＴＣＰ报头格式
     * ０                                                      １５ １６
     * ３１
     * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
     * ｜               源端口号（ｓｏｕｒｃｅ　ｐｏｒｔ）           　｜       　目的端口号（ｄｅｓｔｉｎａｔｉｏｎ　ｐｏｒｔ）     ｜
     * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
     * ｜　　　　　　　　　　　　　　　　　　　　　　　　顺序号（ｓｅｑｕｅｎｃｅ　ｎｕｍｂｅｒ）　　　　　　　　　　　　　　　　　　　　　｜
     * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
     * ｜　　　　　　　　　　　　　　　　　　　　　确认号（ａｃｋｎｏｗｌｅｄｇｅｍｅｎｔ　ｎｕｍｂｅｒ）　　　　　　　　　　　　　　　　　｜
     * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
     * ｜　ＴＣＰ报头　　｜　　保　　            ｜Ｕ｜Ａ｜Ｐ｜Ｒ｜Ｓ｜Ｆ｜                                                     ｜
     * ｜　　　长度　　　｜　　留　　            ｜Ｒ｜Ｃ｜Ｓ｜Ｓ｜Ｙ｜Ｉ｜　　　　　　窗口大小（ｗｉｎｄｏｗ　ｓｉｚｅ）              ｜
     * ｜　　（４位）   ｜　（６位）             ｜Ｇ｜Ｋ｜Ｈ｜Ｔ｜Ｎ｜Ｎ｜                                                     ｜
     * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
     * ｜              校验和（ｃｈｅｃｋｓｕｍ）                     ｜           紧急指针（ｕｒｇｅｎｔ　ｐｏｉｎｔｅｒ）       ｜
     * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
     * ｜                                                选项＋填充（０或多个３２位字）                                    　｜
     * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
     * ｜                                                   数据（０或多个字节）                                            |
     * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
     **/
    public static final int FIN = 1; //FIN：结束标志。
    /**
     * SYN：同步标志。表明同步序列编号栏有效。该标志仅在三次握手建立TCP连接时有效。
     * 它提示TCP连接的服务端检查序列编号，该序列编号为TCP连接初始端（一般是客户端）的初始序列编号。
     * 在这里，可以把TCP序列编号看作是一个范围从0到4，294，967，295的32位计数器。
     * 通过TCP连接交换的数据中每一个字节都经过序列编号。
     * 在TCP报头中的序列编号栏包括了TCP分段中第一个字节的序列编号。
     */
    public static final int SYN = 2;
    public static final int RST = 4; //RST：复位标志。用于复位相应的TCP连接。
    public static final int PSH = 8; //PSH：推标志。该标志置位时，接收端不将该数据进行队列处理，而是尽可能快地将数据转由应用处理。在处理Telnet或rlogin等交互模式的连接时，该标志总是置位的。
    public static final int ACK = 16; //ACK：确认标志。表明确认编号栏有效。大多数情况下该标志位是置位的。TCP报头内的确认编号栏内包含的确认编号（w+1）为下一个预期的序列编号，同时提示远端系统已经成功接收所有数据。
    public static final int URG = 32; //URG：紧急标志。紧急标志为"1"表明该位有效。

    static final short offset_src_port = 0; // 16位源端口
    static final short offset_dest_port = 2; // 16位目的端口
    static final int offset_seq = 4; // 32位序列号
    static final int offset_ack = 8; // 32位确认号
    static final byte offset_lenres = 12; // 4位首部长度/4位保留字
    static final byte offset_flag = 13; // 6位标志位
    static final short offset_win = 14; // 16位窗口大小
    static final short offset_crc = 16; // 16位校验和
    static final short offset_urp = 18; // 16位紧急数据偏移量

    public byte[] m_Data;
    public int m_Offset;

    public TCPHeader(byte[] data, int offset) {
        this.m_Data = data;
        this.m_Offset = offset;
    }

    public int getHeaderLength() {
        int lenres = m_Data[m_Offset + offset_lenres] & 0xFF;
        return (lenres >> 4) * 4;
    }

    public short getSourcePort() {
        return CommonMethods.readShort(m_Data, m_Offset + offset_src_port);
    }

    public void setSourcePort(short value) {
        CommonMethods.writeShort(m_Data, m_Offset + offset_src_port, value);
    }

    public short getDestinationPort() {
        return CommonMethods.readShort(m_Data, m_Offset + offset_dest_port);
    }

    public void setDestinationPort(short value) {
        CommonMethods.writeShort(m_Data, m_Offset + offset_dest_port, value);
    }

    public byte getFlags() {
        return m_Data[m_Offset + offset_flag];
    }

    public short getCrc() {
        return CommonMethods.readShort(m_Data, m_Offset + offset_crc);
    }

    public void setCrc(short value) {
        CommonMethods.writeShort(m_Data, m_Offset + offset_crc, value);
    }

    public int getSeqID() {
        return CommonMethods.readInt(m_Data, m_Offset + offset_seq);
    }

    public int getAckID() {
        return CommonMethods.readInt(m_Data, m_Offset + offset_ack);
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return String.format("%s%s%s%s%s%s%d->%d %s:%s",
                (getFlags() & SYN) == SYN ? "SYN " : "",
                (getFlags() & ACK) == ACK ? "ACK " : "",
                (getFlags() & PSH) == PSH ? "PSH " : "",
                (getFlags() & RST) == RST ? "RST " : "",
                (getFlags() & FIN) == FIN ? "FIN " : "",
                (getFlags() & URG) == URG ? "URG " : "",
                getSourcePort() & 0xFFFF,
                getDestinationPort() & 0xFFFF,
                getSeqID(),
                getAckID());
    }
}
